/*
 * Copyright (C) 2011, EADS France
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 */

package xowl.interpreter.backends.rdf;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 *
 * @author Laurent WOUTERS
 */
public class RDFGraph implements RDFChangeListener {
    private static int NOCHANGE = 0;
    private static int REMOVEONTOLOGY = 1;
    private static int REMOVETRIPLE = 2;
    private static int NOTINONTOLOGY = 3;
    
    private static class Counter {
        private int count;
        public Counter() { count = 1; }
        public void increment() { count++; }
        public boolean decrement() {
            count--;
            return (count == 0);
        }
    }
    
    private static class TripleData {
        private RDFTriple triple;
        private Map<xowl.lang.owl2.Ontology, Counter> counters;
        public RDFTriple getTriple() { return triple; }
        public Collection<xowl.lang.owl2.Ontology> getOntologies() { return counters.keySet(); }
        public TripleData(RDFTriple triple, xowl.lang.owl2.Ontology ontology) {
            this.triple = triple;
            this.counters = new HashMap<xowl.lang.owl2.Ontology, Counter>();
            this.counters.put(ontology, new Counter());
        }
        public boolean addTo(xowl.lang.owl2.Ontology ontology) {
            Counter counter = counters.get(ontology);
            if (counter == null) {
                counter = new Counter();
                counters.put(ontology, counter);
                return true;
            }
            counter.increment();
            return false;
        }
        public int removeFrom(xowl.lang.owl2.Ontology ontology) {
            Counter counter = counters.get(ontology);
            if (counter == null)
                return NOTINONTOLOGY;
            if (!counter.decrement())
                return NOCHANGE;
            counters.remove(ontology);
            if (counters.isEmpty())
                return REMOVETRIPLE;
            else
                return REMOVEONTOLOGY;
        }
        public boolean removeAllFrom(xowl.lang.owl2.Ontology ontology) {
            counters.remove(ontology);
            return (counters.isEmpty());
        }
    }
    
    private class GraphIterator implements Iterator<RDFTriple> {
        private Iterator<xowl.lang.owl2.Ontology> iterOntologies;
        private Iterator<TripleData> inner;
        public GraphIterator() {
            iterOntologies = mapOntologies.keySet().iterator();
            if (iterOntologies.hasNext()) {
                inner = mapOntologies.get(iterOntologies.next()).iterator();
            }
        }
        private void moveNext() {
            while (true) {
                if (inner.hasNext())
                    return;
                if (!iterOntologies.hasNext()) {
                    inner = null;
                    return;
                }
                inner = mapOntologies.get(iterOntologies.next()).iterator();
            }
        }
        @Override public boolean hasNext() { return (inner != null); }
        @Override public RDFTriple next() {
            if (inner == null)
                return null;
            RDFTriple triple = inner.next().getTriple();
            moveNext();
            return triple;
        }
        @Override public void remove() { }
    }
    
    private static class OntologyIterator implements Iterator<RDFTriple> {
        private Iterator<TripleData> inner;
        public OntologyIterator(Iterator<TripleData> inner) {
            this.inner = inner;
        }
        @Override public boolean hasNext() { return inner.hasNext(); }
        @Override public RDFTriple next() { return inner.next().getTriple(); }
        @Override public void remove() { }
    }
    private static class EmptyOntologyIterator implements Iterator<RDFTriple> {
        @Override public boolean hasNext() { return false; }
        @Override public RDFTriple next() { return null; }
        @Override public void remove() { }
    }

    
    
    private Map<String, RDFIRIReference> mapEntities;
    private Map<xowl.lang.owl2.AnonymousIndividual, RDFBlankNode> mapAnonymous;
    private Map<xowl.lang.runtime.Datatype, Map<String, RDFLiteralNode>> mapLiterals;
    private Map<RDFSubjectNode, Map<RDFProperty, Map<RDFNode, TripleData>>> triples;
    private Map<xowl.lang.owl2.Ontology, List<TripleData>> mapOntologies;
    private Map<RDFChangeListener, Integer> priorities;
    private List<RDFChangeListener> listHandlers;
    private RDFChangeset changeset;
    
    public RDFGraph() {
        mapEntities = new HashMap<String, RDFIRIReference>();
        mapAnonymous = new HashMap<xowl.lang.owl2.AnonymousIndividual, RDFBlankNode>();
        mapLiterals = new HashMap<xowl.lang.runtime.Datatype, Map<String, RDFLiteralNode>>();
        triples = new HashMap<RDFSubjectNode, Map<RDFProperty, Map<RDFNode, TripleData>>>();
        mapOntologies = new HashMap<xowl.lang.owl2.Ontology, List<TripleData>>();
        priorities = new HashMap<RDFChangeListener, Integer>();
        listHandlers = new ArrayList<RDFChangeListener>();
    }
    private RDFGraph(RDFGraph parent) {
        mapEntities = new HashMap<String, RDFIRIReference>(parent.mapEntities);
        mapAnonymous = new HashMap<xowl.lang.owl2.AnonymousIndividual, RDFBlankNode>(parent.mapAnonymous);
        mapLiterals = new HashMap<xowl.lang.runtime.Datatype, Map<String, RDFLiteralNode>>(parent.mapLiterals);
        triples = new HashMap<RDFSubjectNode, Map<RDFProperty, Map<RDFNode, TripleData>>>();
        mapOntologies = new HashMap<xowl.lang.owl2.Ontology, List<TripleData>>();
        priorities = new HashMap<RDFChangeListener, Integer>();
        listHandlers = new ArrayList<RDFChangeListener>();
    }

    public void addEventHandler(RDFChangeListener handler, int priority) {
        priorities.put(handler, priority);
        listHandlers.add(handler);
        java.util.Collections.sort(listHandlers, new java.util.Comparator<RDFChangeListener>() {
            @Override public int compare(RDFChangeListener l1, RDFChangeListener l2) {
                int index1 = priorities.get(l1);
                int index2 = priorities.get(l2);
                return (index1 - index2);
            }
        });
    }
    public void removeEventHandler(RDFChangeListener handler) {
        priorities.remove(handler);
        listHandlers.remove(handler);
    }

    public RDFIRIReference getEntityNode(xowl.lang.owl2.IRI iri) {
        if (mapEntities.containsKey(iri.getHasValue()))
            return mapEntities.get(iri.getHasValue());
        RDFIRIReference node = new RDFIRIReference(iri);
        mapEntities.put(iri.getHasValue(), node);
        return node;
    }
    public RDFBlankNode getBlankNode() {
        return new RDFBlankNode(null);
    }
    public RDFBlankNode getBlankNode(xowl.lang.owl2.AnonymousIndividual anon) {
        if (mapAnonymous.containsKey(anon))
            return mapAnonymous.get(anon);
        RDFBlankNode node = new RDFBlankNode(anon);
        mapAnonymous.put(anon, node);
        return node;
    }
    public RDFLiteralNode getLiteralNode(xowl.lang.runtime.Literal lit) {
        String lexical = lit.getLexicalValue();
        xowl.lang.runtime.Datatype datatype = null;
        if (lit.getMemberOf() != null)
            datatype = lit.getMemberOf();
        Map<String, RDFLiteralNode> mapLex = mapLiterals.get(datatype);
        if (mapLex == null) {
            RDFLiteralNode node = new RDFLiteralNode(lit);
            mapLex = new HashMap<String, RDFLiteralNode>();
            mapLex.put(lexical, node);
            mapLiterals.put(datatype, mapLex);
            return node;
        }
        RDFLiteralNode node = mapLex.get(lexical);
        if (node != null)
            return node;
        node = new RDFLiteralNode(lit);
        mapLex.put(lexical, node);
        return node;
    }

    public Iterator<RDFTriple> getIterator() { return new GraphIterator(); }
    public Iterator<RDFTriple> getIterator(xowl.lang.owl2.Ontology ontology) {
        List<TripleData> list = mapOntologies.get(ontology);
        if (list == null)
            return new EmptyOntologyIterator();
        return new OntologyIterator(list.iterator());
    }
    
    public List<RDFTriple> getTriples(RDFSubjectNode subject) {
        List<RDFTriple> result = new ArrayList<RDFTriple>();
        if (!this.triples.containsKey(subject))
            return result;
        for (Map.Entry<RDFProperty, Map<RDFNode, TripleData>> entryProp : this.triples.get(subject).entrySet()) {
            for (Map.Entry<RDFNode, TripleData> entry : entryProp.getValue().entrySet()) {
                result.add(entry.getValue().getTriple());
            }
        }
        return result;
    }
    public List<RDFTriple> getTriples(RDFSubjectNode subject, xowl.lang.owl2.IRI propertyIRI) {
        if (mapEntities.containsKey(propertyIRI.getHasValue()))
            return getTriples(subject, mapEntities.get(propertyIRI.getHasValue()));
        return null;
    }
    public List<RDFTriple> getTriples(RDFSubjectNode subject, RDFProperty property) {
        List<RDFTriple> result = new ArrayList<RDFTriple>();
        if (!this.triples.containsKey(subject))
            return result;
        Map<RDFProperty, Map<RDFNode, TripleData>> mapProp = this.triples.get(subject);
        if (!mapProp.containsKey(property))
            return result;
        for (Map.Entry<RDFNode, TripleData> entry : mapProp.get(property).entrySet())
            result.add(entry.getValue().getTriple());
        return result;
    }
    
    @Override public void onChange(xowl.interpreter.impl.AggregatedEvaluator evaluator, RDFChange change) {
        this.changeset = new RDFChangeset();
        if (change.isAdd()) addTriple(change);
        else removeTriple(change);
        notifyListeners(evaluator);
    }
    @Override public void onChanges(xowl.interpreter.impl.AggregatedEvaluator evaluator, RDFChangeset changeset) {
        this.changeset = new RDFChangeset();
        for (RDFChange change : changeset.getRDFChanges()) {
            if (change.isAdd()) addTriple(change);
            else removeTriple(change);
        }
        notifyListeners(evaluator);
    }
    
    private void addTriple(RDFChange change) {
        RDFTriple triple = change.getTriple();
        xowl.lang.owl2.Ontology ontology = change.getOntology();
        if (triples.containsKey(triple.getSubject())) {
            Map<RDFProperty, Map<RDFNode, TripleData>> mapProp = triples.get(triple.getSubject());
            if (mapProp.containsKey(triple.getProperty())) {
                Map<RDFNode, TripleData> mapObject = mapProp.get(triple.getProperty());
                TripleData data = mapObject.get(triple.getObject());
                if (data == null) {
                    data = new TripleData(triple, ontology);
                    mapObject.put(triple.getObject(), data);
                    onAddToOntology(data, change);
                } else if (data.addTo(ontology))
                    onAddToOntology(data, change);
            } else {
                TripleData data = new TripleData(triple, ontology);
                Map<RDFNode, TripleData> mapObject = new HashMap<RDFNode, TripleData>();
                mapObject.put(triple.getObject(), data);
                mapProp.put(triple.getProperty(), mapObject);
                onAddToOntology(data, change);
            }
        } else {
            TripleData data = new TripleData(triple, ontology);
            Map<RDFNode, TripleData> mapObject = new HashMap<RDFNode, TripleData>();
            Map<RDFProperty, Map<RDFNode, TripleData>> mapProp = new HashMap<RDFProperty, Map<RDFNode, TripleData>>();
            mapObject.put(triple.getObject(), data);
            mapProp.put(triple.getProperty(), mapObject);
            triples.put(triple.getSubject(), mapProp);
            onAddToOntology(data, change);
        }
    }
    
    private void onAddToOntology(TripleData data, RDFChange change) {
        List<TripleData> lTriples = mapOntologies.get(change.getOntology());
        if (lTriples == null) {
            lTriples = new ArrayList<TripleData>();
            mapOntologies.put(change.getOntology(), lTriples);
        }
        lTriples.add(data);
        addToChangeset(change);
    }
    
    private void removeTriple(RDFChange change) {
        RDFTriple triple = change.getTriple();
        xowl.lang.owl2.Ontology ontology = change.getOntology();
        Map<RDFProperty, Map<RDFNode, TripleData>> mapProp = triples.get(triple.getSubject());
        if (mapProp == null)
            return;
        Map<RDFNode, TripleData> mapObject = mapProp.get(triple.getProperty());
        if (mapObject == null)
            return;
        TripleData existing = mapObject.get(triple.getObject());
        if (existing == null)
            return;
        int result = existing.removeFrom(ontology);
        if (result == NOTINONTOLOGY)
            return;
        if (result == NOCHANGE)
            return;
        else if (result == REMOVEONTOLOGY)
            onRemoveFromOntology(existing, change);
        else if (result == REMOVETRIPLE) {
            mapObject.remove(triple.getObject());
            if (mapObject.isEmpty())
                mapProp.remove(triple.getProperty());
            if (mapProp.isEmpty())
                triples.remove(triple.getSubject());
            onRemoveFromOntology(existing, change);
        }
    }
    
    private void onRemoveFromOntology(TripleData data, RDFChange change) {
        List<TripleData> lTriples = mapOntologies.get(change.getOntology());
        lTriples.remove(data);
        if (lTriples.isEmpty())
            mapOntologies.remove(change.getOntology());
        addToChangeset(change);
    }
    
    private void addToChangeset(RDFChange change) {
        changeset.add(change);
    }
    
    public void removeOntology(xowl.lang.owl2.Ontology ontology) {
        if (!mapOntologies.containsKey(ontology))
            return;
        for (TripleData data : mapOntologies.get(ontology)) {
            if (data.removeAllFrom(ontology)) {
                Map<RDFProperty, Map<RDFNode, TripleData>> mapProp = triples.get(data.getTriple().getSubject());
                Map<RDFNode, TripleData> mapObject = mapProp.get(data.getTriple().getProperty());
                mapObject.remove(data.getTriple().getObject());
                if (mapObject.isEmpty())
                    mapProp.remove(data.getTriple().getProperty());
                if (mapProp.isEmpty())
                    triples.remove(data.getTriple().getSubject());
                addToChangeset(new RDFChange(data.getTriple(), ontology, false));
            }
        }
        mapOntologies.remove(ontology);
    }
    
    private void notifyListeners(xowl.interpreter.impl.AggregatedEvaluator evaluator) {
        int size = changeset.getRDFChanges().size();
        if (size == 0)
            return;
        if (size == 1) {
            RDFChange change = changeset.getRDFChanges().iterator().next();
            for (RDFChangeListener listener : listHandlers)
                listener.onChange(evaluator, change);
        }
        else {
            RDFChangeset copy = changeset;
            for (RDFChangeListener listener : listHandlers)
                listener.onChanges(evaluator, copy);
        }
    }
}
